use base64::{decode, encode};
use chrono::offset::Utc;
use chrono::DateTime;
use crypto::buffer::{BufferResult, ReadBuffer, WriteBuffer};
use crypto::{aes, blockmodes, buffer, symmetriccipher};
use std::convert::TryInto;
use std::sync::MutexGuard;
use std::time::SystemTime;

// AES-256/CBC/Pkcs encryption.
fn encrypt(
    data: &[u8],
    key: &[u8],
    iv: &[u8],
) -> Result<Vec<u8>, symmetriccipher::SymmetricCipherError> {
    // type available for the platform.
    let mut encryptor =
        aes::cbc_encryptor(aes::KeySize::KeySize256, key, iv, blockmodes::PkcsPadding);

    // read from or written to them.
    let mut final_result = Vec::<u8>::new();
    let mut read_buffer = buffer::RefReadBuffer::new(data);
    let mut buffer = [0; 4096];
    let mut write_buffer = buffer::RefWriteBuffer::new(&mut buffer);

    loop {
        let result = encryptor.encrypt(&mut read_buffer, &mut write_buffer, true)?;

        final_result.extend(
            write_buffer
                .take_read_buffer()
                .take_remaining()
                .iter()
                .map(|&i| i),
        );

        match result {
            BufferResult::BufferUnderflow => break,
            BufferResult::BufferOverflow => {}
        }
    }

    Ok(final_result)
}

// AES-256/CBC/Pkcs encryption.
fn decrypt(
    encrypted_data: &[u8],
    key: &[u8],
    iv: &[u8],
) -> Result<Vec<u8>, symmetriccipher::SymmetricCipherError> {
    let mut decryptor =
        aes::cbc_decryptor(aes::KeySize::KeySize256, key, iv, blockmodes::PkcsPadding);

    let mut final_result = Vec::<u8>::new();
    let mut read_buffer = buffer::RefReadBuffer::new(encrypted_data);
    let mut buffer = [0; 4096];
    let mut write_buffer = buffer::RefWriteBuffer::new(&mut buffer);

    loop {
        let result = decryptor.decrypt(&mut read_buffer, &mut write_buffer, true)?;
        final_result.extend(
            write_buffer
                .take_read_buffer()
                .take_remaining()
                .iter()
                .map(|&i| i),
        );
        match result {
            BufferResult::BufferUnderflow => break,
            BufferResult::BufferOverflow => {}
        }
    }

    Ok(final_result)
}

fn from_slice(bytes: &[u8]) -> [u8; 32] {
    let mut array = [0; 32];
    let bytes = &bytes[..array.len()]; // panics if not enough data
    array.copy_from_slice(bytes);
    array
}

fn get_iv_by_time() -> [u8; 16] {
    let system_time = SystemTime::now();
    let datetime: DateTime<Utc> = system_time.into();
    let a = &format!("{}", datetime.format("%Y-%m-%d-%H-%M"))[..16];
    let mut b = [0; 16];
    b.copy_from_slice(a.as_bytes());
    b
}

pub struct Encryptor {
    key: [u8; 32],
    pub raw_key:String,
}

pub trait Cryptor {
    // add code here
    fn encrypt(&self, data: &[u8]) -> Vec<u8>;
    fn decrypt(&self, date: &[u8]) -> Vec<u8>;
    fn en_b64(&self, data: &[u8]) -> String;
    fn de_b64(&self, b64str: &str) -> Vec<u8>;
}

impl Encryptor {
    fn iv(&self) -> [u8; 16] {
        get_iv_by_time()
    }

    pub fn new(key: &str) -> Encryptor {
        // let iv: [u8; 16] = [0; 16];

        let ikey: [u8; 32];

        let mut okey: Vec<u8> = key.to_string().into_bytes();
        if key.len() < 32 {
            let mut o: Vec<u8> = (0..(32 - key.len()) as u8).collect();
            okey.append(&mut o);
            ikey = from_slice(okey.as_slice());
        } else {
            ikey = key
                .as_bytes()
                .try_into()
                .expect("slice with incorrect length");
        };
        Encryptor { 
            key: ikey , 
            raw_key:key.to_string()
        }
    }
    // add code here
}
trait B64 {
    // add code here
    fn to64(&self) -> String;
    fn from64(&self) -> Vec<u8>;
}

impl B64 for String {
    fn to64(&self) -> String {
        encode(self.as_bytes())
    }

    fn from64(&self) -> Vec<u8> {
        decode(self.as_bytes()).expect("not b64 str")
    }
    // add code here
}

impl Cryptor for MutexGuard<'_, Encryptor> {
    fn encrypt(&self, data: &[u8]) -> Vec<u8> {
        let encrypted_data = encrypt(data, &self.key, &self.iv()).ok().unwrap();
        encrypted_data
    }

    fn decrypt(&self, data: &[u8]) -> Vec<u8> {
        match decrypt(data, &self.key, &self.iv()) {
            Ok(s) => s,
            _ => vec![],
        }
    }

    fn en_b64(&self, data: &[u8]) -> String {
        // let data_vec = decode(b64str.as_bytes()).expect("not b64 str");
        let enc_data = self.encrypt(data);
        encode(&enc_data)
    }

    fn de_b64(&self, b64str: &str) -> Vec<u8> {
        let data_vec = decode(b64str.as_bytes()).expect("not b64 str");
        self.decrypt(&data_vec)
    }
}

impl Cryptor for Encryptor {
    // add code here
    fn encrypt(&self, data: &[u8]) -> Vec<u8> {
        let encrypted_data = encrypt(data, &self.key, &self.iv()).ok().unwrap();
        encrypted_data
    }

    fn decrypt(&self, data: &[u8]) -> Vec<u8> {
        match decrypt(data, &self.key, &self.iv()) {
            Ok(s) => s,
            _ => vec![],
        }
    }

    fn en_b64(&self, data: &[u8]) -> String {
        // let data_vec = decode(b64str.as_bytes()).expect("not b64 str");
        let enc_data = self.encrypt(data);
        encode(&enc_data)
    }

    fn de_b64(&self, b64str: &str) -> Vec<u8> {
        let data_vec = decode(b64str.as_bytes()).expect("not b64 str");
        self.decrypt(&data_vec)
    }
}

// #[cfg(test)]
pub fn test_enc() {
    let message = "Hello World! ... Ich liebe dich!";
    let key = "hello world key ";

    let enc = Encryptor::new(key);
    println!(
        "iv : {}",
        String::from_utf8(enc.iv().to_vec()).expect("some error")
    );
    let encrypted_data = enc.encrypt(message.as_bytes());
    println!("enc : {:?}", encrypted_data);
    let decrypted_data = enc.decrypt(encrypted_data.as_slice());
    assert!(message.as_bytes() == &decrypted_data[..]);

    let encrypted_data = enc.encrypt(message.as_bytes());
    println!("enc : {:?}", encrypted_data);
    let decrypted_data = enc.decrypt(encrypted_data.as_slice());
    assert!(message.as_bytes() == &decrypted_data[..]);

    let encrypted_data = enc.en_b64(message.as_bytes());
    println!("en b64 c : {}", encrypted_data);
    let decrypted_data = enc.de_b64(&encrypted_data);
    assert!(message.as_bytes() == &decrypted_data[..]);
    println!(
        "de b64 d : {}",
        String::from_utf8(decrypted_data).expect("not support utf8")
    );
    // let encrypted_data = encrypt(message.as_bytes(), &key, &iv).ok().unwrap();
    // let decrypted_data = decrypt(&encrypted_data[..], &key, &iv).ok().unwrap();

    // assert!(message.as_bytes() == &decrypted_data[..]);
}
